﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Windows.Controls;
using DiagramCreator.Nodes;
using System.Windows.Input;
using DiagramCreator.Arrows;
using System.Windows.Media;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.ComponentModel;
using DiagramCreator.Utils.Events;

namespace DiagramCreator.Diagram
{
    public partial class DiagramSheet : Canvas, IDisposable
    {
        private bool NeedsSave
        {
            get;
            set;
        }

        public bool AutoSizing { get; set; }

        private bool showGridLines;
        public bool ShowGridLines
        {            
            set 
            {
                showGridLines = value;
                if (value)
                {

                }
            }
            get { return showGridLines; }
        }

        //Polygon CreateGridPolygon(double width, double height, double stepSize)
        //{
        //    double currentColumnOffset = 0.0;
            
        //}

        #region - Konstruktor -

        public DiagramSheet()
        {            
            this.MouseMove += new System.Windows.Input.MouseEventHandler(DiagramSheet_MouseMove);            
            this.MouseDown += new MouseButtonEventHandler(DiagramSheet_MouseDown);
            this.MouseUp += new MouseButtonEventHandler(DiagramSheet_MouseUp);
            createFirstElement();
            this.LayoutUpdated += new EventHandler(DiagramSheet_LayoutUpdated);

            AutoSizing = true;
        }

        #endregion

        private string mTitle;
        public string Title
        {
            private set
            {
                mTitle = value;
                if (DiagramTitleChanged != null)
                {
                    ObjectEventArgs<string> args = new ObjectEventArgs<string>();
                    args.Argument = value;
                    DiagramTitleChanged(this, args); // rzuć zdarzenie zmiany tytułu
                }
            }
            get { return mTitle; }
        }

        public event EventHandler<ObjectEventArgs<string>> DiagramTitleChanged;        

        void DiagramSheet_LayoutUpdated(object sender, EventArgs e)
        {
            if (focusLostElement == null)
            {
                createFocusElement();
            }
        }

        #region - createFocusElement -

        void createFocusElement()
        {
            focusLostElement = (UIElement)((ScrollViewer)this.Parent).Parent;
        }

        void b_KeyUp(object sender, KeyEventArgs e)
        {
            KeyUpHandler(e);
        }

        void b_KeyDown(object sender, KeyEventArgs e)
        {
            KeyDownHandler(e);
        }

        #endregion

        UIElement focusLostElement;
        double oldMouseX, oldMouseY;
        bool MovingState = false;
        bool ConnectToState = false;
        bool RectangleSelectMode = false;
        bool MultipleSelectMode = false;
        double selectFromX, selectFromY;
        GenericNode draggingNode = null;
        double mouseX, mouseY;

        private List<GenericNode> selectedNodes = new List<GenericNode>();
        private Stack<Cursor> cursorStack = new Stack<Cursor>();
        private GenericNode resizingNode;
        private Shape rectSelect;

        #region - KeyDownHandler -

        public void KeyDownHandler(KeyEventArgs args)
        {
            Key k;
            if (args.Key == Key.System)
            {
                k = args.SystemKey;
            }
            else
            {
                k = args.Key;
            }

            if (k == Key.Delete)
            {
                DeleteSelectedNodes();
            }
            if (k == Key.LeftShift || k == Key.RightShift)
            {
                if (!ConnectToState)
                {
                    // Przejście w stan podlączania noda do innego
                    ConnectToState = true;
                    cursorStack.Push(Cursor);
                    Cursor = Cursors.Cross;
                }
            }
            if (k == Key.RightCtrl || k == Key.LeftCtrl)
            {
                MultipleSelectMode = true;
            }
            if (k == Key.Space)
            {
                AlignCenterVertiacally(selectedNodes);
            }
        }

        #endregion

        #region - KeyUpHandler -

        public void KeyUpHandler(KeyEventArgs args)
        {
            Key k;
            if (args.Key == Key.System)
            {
                k = args.SystemKey;
            }
            else
            {
                k = args.Key;
            }
            if (k == Key.LeftShift || k == Key.RightShift)
            {
                if (ConnectToState)
                {   
                    // wyjscie ze stanu podlaczania noda
                    ConnectToState = false;
                    Cursor = cursorStack.Pop();
                }
            }
            if (k == Key.RightCtrl || k == Key.LeftCtrl)
            {
                MultipleSelectMode = false;
            }
        }

        #endregion

        #region - DiagramSheet_MouseDown -

        private void DiagramSheet_MouseDown(object sender, MouseButtonEventArgs e)
        {
            
            if (!MultipleSelectMode)
            {
                DeselectAllNodes();
                Keyboard.Focus(focusLostElement);
            }
            if (!RectangleSelectMode)
            {
                BeginRectangleSelect(e.GetPosition(this).X, e.GetPosition(this).Y);
            }
        }

        #endregion

        #region - DiagramSheet_MouseUp -

        void DiagramSheet_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (RectangleSelectMode)
            {
                EndRectangleSelect(e.GetPosition(this).X, e.GetPosition(this).Y);
            }
        }

        #endregion

        #region - EndRectangleSelect -

        private void EndRectangleSelect(double x, double y)
        {
            this.ReleaseMouseCapture();
            RectangleSelectMode = false;
            Children.Remove(rectSelect);
            // sprawdzenie który z nodów znajduje się w obrębie prostokąta
            double top, left;
            GenericNode tmpNode;
            double rectTop, rectBottom, rectLeft, rectRight;
            
            rectTop = (double)rectSelect.GetValue(Canvas.TopProperty);
            rectBottom = rectTop + rectSelect.Height;
            rectLeft = (double)rectSelect.GetValue(Canvas.LeftProperty);
            rectRight = rectLeft + rectSelect.Width;
            if (!MultipleSelectMode)
            {
                // tworzenie nowego zaznaczenia
                foreach (GenericNode node in selectedNodes)
                {
                    node.Selected = false;
                }
                selectedNodes.Clear();
            }
            foreach (object obj in Children)
            {
                if (obj is GenericNode)
                {
                    tmpNode = (GenericNode)obj;
                    top = (double)tmpNode.GetValue(Canvas.TopProperty) + tmpNode.Height / 2.0;
                    left = (double)tmpNode.GetValue(Canvas.LeftProperty) + tmpNode.Width / 2.0;
                    // zaznaczam jesli górny-lewy róg jest w obszarze
                    if (left >= rectLeft
                        && left <= rectRight
                        && top >= rectTop
                        && top <= rectBottom)
                    {
                        tmpNode.Selected = true;
                        if (!selectedNodes.Contains(tmpNode))
                        {
                            selectedNodes.Add(tmpNode);
                        }
                    }
                }
            }
        }

        #endregion

        #region - BeginRectangleSelect -

        /// <summary>
        /// Rozpoczyna zaznaczanie obszaru na diagramie
        /// </summary>
        /// <param name="x">współrzędna X kursora myszy</param>
        /// <param name="y">współrzędna Y kursora myszy</param>
        private void BeginRectangleSelect(double x, double y)
        {
            // stworzenie prostokąta do zaznaczania
            rectSelect = new Rectangle();
            //rectSelect.Fill.Opacity = 0.1;
            rectSelect.Stroke = Brushes.LightBlue;
            rectSelect.StrokeThickness = 2.0;
            rectSelect.Fill = null;           
            // ustawienie polozenia prostokata
            rectSelect.SetValue(Canvas.LeftProperty, x);
            rectSelect.SetValue(Canvas.TopProperty, y);
            Children.Add(rectSelect);
            // złapanie myszy aby powiązać pozycją kursora z rogami prostokąta
            CaptureMouse();
            // wejście w stan RectangleSelectMode
            RectangleSelectMode = true;
            // zapisanie miejsca rozpoczęcia zaznaczania
            selectFromX = x;
            selectFromY = y;
        }

        #endregion

        #region - First Node -

        GenericNode firstNode;

        void createFirstElement()
        {
            firstNode = new StartNode();
            firstNode.Width = 50.0;
            firstNode.Height = 50.0;
            firstNode.SetValue(Canvas.LeftProperty, 50.0);
            firstNode.SetValue(Canvas.TopProperty, 80.0);

            addListenersToNode(firstNode);
            firstNode.CanHaveMultipleChildren = false;
            this.Children.Add(firstNode);
        }

        #endregion

        #region - DiagramSheet_MouseMove -

        void DiagramSheet_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            mouseX = e.GetPosition(this).X;
            mouseY = e.GetPosition(this).Y;
            // przesuwanie zaznaczonych elementow
            double x = mouseX - oldMouseX;
            double y = mouseY - oldMouseY;
            if (MovingState)
            {                
                moveSelectedNodes(x, y);

            }
            else
            {
                // Przesuwanie nowo-utworzonoego elementu
                if (draggingNode != null)
                {
                    if (!moveNodeByVector(draggingNode, x, y))
                    {
                        endDragChildNode();
                    }
                }
                else if (RectangleSelectMode)
                {
                    // zmiana rozmiarów prostokąta zaznaczenia
                    UpdateRectangleSelect(mouseX, mouseY);
                }
            }
            oldMouseX = mouseX;
            oldMouseY = mouseY;
        }

        #endregion

        #region - UpdateRectangleSelect -

        private void UpdateRectangleSelect(double x, double y)
        {           
            rectSelect.Height = Math.Abs(selectFromY - y);
            rectSelect.Width = Math.Abs(selectFromX - x);
            if (selectFromY > y)
            {
                rectSelect.SetValue(Canvas.TopProperty, y);
            }
            else
            {
                rectSelect.SetValue(Canvas.TopProperty, selectFromY);
            }
            if (selectFromX > x)
            {
                rectSelect.SetValue(Canvas.LeftProperty, x);
            }
            else
            {
                rectSelect.SetValue(Canvas.LeftProperty, selectFromX);
            }
        }

        #endregion

        #region - beginMoveNodes -

        void beginMoveNodes()
        {
            if (!MovingState)
            {
                cursorStack.Push(Cursor);
                Cursor = Cursors.SizeAll;
                oldMouseX = mouseX;
                oldMouseY = mouseY;
                MovingState = true;
            }
        } 

        #endregion

        #region - endMoveNodes -

        void endMoveNodes()
        {
            if (MovingState)
            {
                MovingState = false;
                Cursor = cursorStack.Pop();
                if (AutoSizing)
                    AdjustSizeToChildrenControls();
            }
        }

        #endregion

        #region - beginDragChildNode -

        void beginDragChildNode(GenericNode parent, NODE_TYPE t)
        {
            NeedsSave = true;
            if (t == NODE_TYPE.ACTION_NODE)
            {
                draggingNode = new ActionNode();
                draggingNode.Width = 100;
                draggingNode.Height = 60;
                ConnectNodes(parent, draggingNode);
            }
            if (t == NODE_TYPE.DECISSION_NODE)
            {
                draggingNode = new DecissionNode();
                draggingNode.Width = 100;
                draggingNode.Height = 60;
                ConnectNodes(parent, draggingNode);
            }
            if (t == NODE_TYPE.NOTE_NODE)
            {
                draggingNode = new NoteNode();
                draggingNode.Width = 80;
                draggingNode.Height = 50;
                ConnectNote(draggingNode, parent);
            }
            if (t == NODE_TYPE.END_POINT)
            {
                draggingNode = new StopNode();
                draggingNode.Width = 50.0;
                draggingNode.Height = 50.0;
                ConnectNodes(parent, draggingNode);
            }
            // ustawienie elementu, który uzyskuje focus, w przypadku gdy nod traci zaznaczenie
            draggingNode.FocusLostElement = focusLostElement;

            // schowanie polaczen nowego elementu - na czas przesuwania
            draggingNode.HideConnectionLines();

            // ustawienie pozycji nowo utworzonego elementu
            SetNewlyCreatedNodePosition(mouseX, mouseY, draggingNode);
            
            // zapiecie obslugi zdarzenia MouseUp
            // aby moc zakonczyc przesuwanie elementu
            draggingNode.MouseUp += new MouseButtonEventHandler(draggingNode_MouseUp);

            // dodanie zestawu listenerów dla nowego nodas
            addListenersToNode(draggingNode);

            // i dodanie go do layoutu
            this.Children.Add(draggingNode);
        }

        #endregion

        #region - draggingNode_MouseUp -

        void draggingNode_MouseUp(object sender, MouseButtonEventArgs e)
        {
            endDragChildNode();
        }

        #endregion

        #region - n_MouseDown -

        /// <summary>
        /// kliknięcie myszą na nodzie (każdym)
        /// </summary>
        void n_MouseDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true; // aby nie przekazywać zdarzenia dalej - do this
            GenericNode n = (GenericNode)sender;
            if (!n.Selected)
            {
                if (MultipleSelectMode)
                {
                    // dodanie do istniejacego zbioru elementów zaznaczonych
                    selectNode(n);
                }
                else
                {
                    if (ConnectToState)
                    {
                        if (selectedNodes.Count == 1)
                        {
                            if (e.LeftButton == MouseButtonState.Pressed)
                            {
                                // podłączam do tego noda
                                ConnectNodes(selectedNodes[0], n);
                            }
                            else
                            {
                                RemoveConnection(selectedNodes[0], n);
                            }
                        }
                    }
                    else
                    {
                        // stworzenie nowego zbioru zaznaczeń z tylko tym jednym elementem
                        deselectNodes(selectedNodes);
                        selectNode(n);
                    }
                }                
            }
            else
            {
                if (!MultipleSelectMode)
                {
                    // zaznaczenie tylko tego noda
                    deselectNodes(selectedNodes);
                    selectNode(n);
                }
            }
        }

        #endregion

        #region - ConnectNodes -

        public ArrowLine ConnectNodes(GenericNode parent, GenericNode child)
        {
            NeedsSave = true;

            if (child.ParentNodes == null)
                child.ParentNodes = new List<GenericNode>();
            child.ParentNodes.Add(parent);
            // ..i ewentualne połączenie między obecnymi parent i child
            RemoveConnection(parent, child);
            // dodanie child do listy dzieci noda parent
            parent.ChildNodes.Add(child);

            // stworzenie linii laczacej parenta z wlasnie utworzonym elementem
            ArrowLine connector = parent.CreateConnectorLineTo(child);
            // i dodanie linii do diagramu
            this.Children.Add(connector);
            connector.updatePosition(); // aby pokazac we wlasciwym miejscu
            return connector;
        }
        #endregion

        #region - ConnectNote -

        public void ConnectNote(GenericNode note, GenericNode child)
        {
            NeedsSave = true;

            RemoveConnection(child, note);
            // dodanie child do listy dzieci noda parent
            child.ChildNodes.Add(note);
            ConnectionLineForNote connector = child.CreateNoteConnectorLineTo(note);
            // i dodanie linii do diagramu
            this.Children.Add(connector);
            connector.updatePosition(); // aby pokazac we wlasciwym miejscu
        }

        #endregion

        #region - RemoveConnection -
        /// <summary>
        /// Usuwa połączenie pomiędzy nodami
        /// </summary>
        public void RemoveConnection(GenericNode parent, GenericNode child)
        {
            NeedsSave = true;

            if (parent != null && child != null && child.ConnectionLines.ContainsKey(parent))
            {
                GenericConnectionLine line = child.ConnectionLines[parent]; // linia do usuniecia
                
                if (line != null)
                {
                    // usuniećie linii z diagramu
                    this.Children.Remove(line);
                }
                // usnięcie child z listy dzieci elementu parent
                parent.ChildNodes.Remove(child);
                // usunięcie linii z listy połączeń elementu child
                child.ConnectionLines.Remove(parent);
            }
        }

        #endregion

        #region - n_ShapeMouseUp -

        void n_ShapeMouseUp(object sender, MouseButtonEventArgs e)
        {
            endMoveNodes();
        }

        #endregion

        #region - n_ShapeMouseDown -

        void n_ShapeMouseDown(object sender, MouseButtonEventArgs e)
        {
            beginMoveNodes();
            // e.Handled = true;
        }

        #endregion

        #region - void SetNewlyCreatedNodePosition(double x, double y) -

        void SetNewlyCreatedNodePosition(double x, double y, GenericNode node)
        {
            if (node != null)
            {
                node.SetValue(Canvas.TopProperty, y - 40); // -40 so the mouse cursor be above the dragging element
                node.SetValue(Canvas.LeftProperty, x - (node.Width / 2));
            }
        }

        #endregion

        #region - moveSelectedNodes -

        /// <summary>
        /// Przesuwa o zadany wektor zaznaczone elementy
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>

        void moveSelectedNodes(double x, double y)
        {            
            foreach (GenericNode n in selectedNodes)
            {
                if (!moveNodeByVector(n, x, y))
                {
                    endMoveNodes();
                    break;
                }
            }
        }

        #endregion

        #region - moveNodeByVector -

        bool moveNodeByVector(GenericNode n, double x, double y)
        {
            NeedsSave = true;

            double newLeft = (double)n.GetValue(Canvas.LeftProperty) + x;
            if (newLeft > 0)
                n.SetValue(Canvas.LeftProperty, newLeft);
            else
            {
                n.SetValue(Canvas.LeftProperty, 1.0);
                // return false;                
            }
            double newTop = (double)n.GetValue(Canvas.TopProperty) + y;
            if (newTop > 0)
                n.SetValue(Canvas.TopProperty, newTop);
            else
            {
                n.SetValue(Canvas.TopProperty, 1.0);
                // return false;               
            }

            n.UpdateConnectionLines();
            return true;
        }

        #endregion

        #region - void addListenersToNode(GenericNode n) -

        public void addListenersToNode(GenericNode n)
        {
            n.MouseDown += new MouseButtonEventHandler(n_MouseDown);
            n.ChildControlBeginDrag += new GenericNode.BeginDragDelegate(beginDragChildNode);
            n.ShapeMouseDown += new MouseButtonEventHandler(n_ShapeMouseDown);
            n.ShapeMouseUp += new MouseButtonEventHandler(n_ShapeMouseUp);
            //n.Shape.MouseEnter += new MouseEventHandler(n_MouseEnter);
            //n.Shape.MouseEnter += new MouseEventHandler(n_MouseLeave);
            //n.Shape.MouseMove += new MouseEventHandler(n_MouseMove);
        }

        #endregion

        #region - n_MouseMove -

        void n_MouseMove(object sender, MouseEventArgs e)
        {
            //GenericNode node = (GenericNode)((Grid)((Grid)((Shape)sender).Parent).Parent).Parent;
            
            //if (ConnectToState && !node.CanHaveMultipleParents && node.ConnectionLines.Count > 0)
            //{
            //    if (Cursor != Cursors.No)
            //    {
            //        Cursor = Cursors.No;
            //    }
            //}
        }

        #endregion

        #region - n_MouseEnter -

        void n_MouseEnter(object sender, MouseEventArgs e)
        {
            //GenericNode node = (GenericNode)((Grid)((Grid)((Shape) sender).Parent).Parent).Parent;
            //if (ConnectToState && !node.CanHaveMultipleParents && node.ConnectionLines.Count > 0)
            //{
            //    cursorStack.Push(Cursor);
            //    Cursor = Cursors.No;
            //}
        }

        #endregion

        #region - n_MouseLeave -

        private void n_MouseLeave(object sender, MouseEventArgs args)
        {
            //GenericNode node = (GenericNode)((Grid)((Grid)((Shape)sender).Parent).Parent).Parent;
            //if (ConnectToState && !node.CanHaveMultipleParents && node.ConnectionLines.Count > 0)
            //{
            //    Cursor = cursorStack.Pop();
            //}
        }

        #endregion

        #region - void endDragChildNode() -

        void endDragChildNode()
        {
            if (draggingNode != null)
            {
                DeselectAllNodes(); // only newly created node will be selected
                draggingNode.EndDrag();
                draggingNode.MouseUp -= new MouseButtonEventHandler(draggingNode_MouseUp);
                // update polozenia linii
                // i pokazanie polaczeń
                draggingNode.UpdateConnectionLines();
                draggingNode.ShowConnectionLines();                
                draggingNode.NodeTextField.Focusable = true;                
                draggingNode.NodeTextField.Focus();
                selectNode(draggingNode);
                draggingNode.NodeTextField.SelectAll();
            }
            draggingNode = null;
        }

        #endregion

        #region - Select/Deselect Nodes -

        public void selectNode(GenericNode n)
        {
            n.Selected = true;
            if (!selectedNodes.Contains(n))
            {
                selectedNodes.Add(n);
            }
        }

        void deselectNode(GenericNode n)
        {
            n.Selected = false;
            if (selectedNodes.Contains(n))
            {
                selectedNodes.Remove(n);
            }
        }

        void deselectNodes(List<GenericNode> l)
        {
            foreach (GenericNode n in l)
            {
                n.Selected = false;
            }
            l.Clear();
        }

        #endregion

        #region - Deselect all nodes -

        public void DeselectAllNodes()
        {
            deselectNodes(selectedNodes);
        }

        #endregion

        #region - DeleteNode -

        private void DeleteNode(GenericNode node)
        {
            NeedsSave = true;

            // usuwanie noda z diagramu
            // pod warunkiem, że nie jest to ostatni node typu StartNode
            if (!(node is StartNode && Children.OfType<StartNode>().Count() == 1))
                Children.Remove(node);
            // usuwam wszystkie jego polaczenia z parentami (ConnectionLines)
            if (node.ConnectionLines != null)
            {
                foreach (GenericConnectionLine l in node.ConnectionLines.Values)
                {
                    Children.Remove(l);
                    l.Dispose();
                }
            }
            // i dziećmi
            if (node.ChildNodes != null)
            {
                foreach (GenericNode n in node.ChildNodes)
                {
                    if (n.ConnectionLines != null)
                    {
                        Children.Remove(n.ConnectionLines[node]);
                        n.ConnectionLines[node].Dispose();
                    }
                }
            }
            if (node.ParentNodes != null)
                foreach (GenericNode p in node.ParentNodes)
                    p.ChildNodes.Remove(node);
                
            node = null;
        }

        #endregion

        #region - DeleteSelectedNodes -

        private void DeleteSelectedNodes()
        {
            foreach (GenericNode node in selectedNodes)
            {
                DeleteNode(node);
            }
        }

        #endregion        

        #region - IDisposable -

        public void Dispose()
        {
            
        }

        #endregion
    }
}
